// C source file with the function definitions to handle byte arrays

// check_byte_array()
// function to check the paramaters of a byte array
bool check_byte_array(byte * arr, umax size)
{
  return (arr != NULL && size != 0);
}

// print_byte_array()
// function to print a byte array
umax print_byte_array(byte * arr, umax size)
{
  // check params
  if (check_byte_array(arr, size) == false)
    return 0;
  
  // print the array
  for (umax i = 0; i < size; i++) {
    print_byte_hex(arr + i, size - i);
    putchar(' ');
  }
  putchar('\n');
  return size;
}

// check_byteshift_type()
// function to check if an integer is a valid byteshift_type
bool check_byteshift_type(byteshift_type shift)
{
  return (shift == BYTESHIFT_LEFT || shift == BYTESHIFT_RIGHT);
}

// cp_mem_bytes()
// function to copy bytes from a place to another
// will return a pointer to the position after the last write position
byte * cp_mem_bytes(byte * src, umax length, byte * dest)
{
  // check function parameters
  if (check_byte_array(src, length) == false || check_byte_array(dest, length) == false)
    return NULL;
  
  // otherwise copy the bytes
  for (umax i = 0; i < length; i++)
    dest[i] = src[i];
  return dest + length;
}

// comp_bytes_until_mismatch()
// compares the bytes of 2 arrays until a mismatch is found
// the function will return an integer indicating the position
// of the element that interrupts the comparison.
// If there is no interruption, size will be returned.
umax comp_bytes_until_mismatch(byte * arr1, byte * arr2, umax size)
{
  // check params
  if (check_byte_array(arr1, size) == false || check_byte_array(arr2, size) == false)
    return 0;
  
  // make the comparison
  umax int_pos = 0;
  for (umax i = 0; i < size; i++)
    if (*(arr1++) == *(arr2++))
      int_pos++;
    else
      break;
  return int_pos;
}

// comp_bytes() function
// function to compare 2 byte arrays
bool comp_bytes(byte * arr1, byte * arr2, umax size)
{
  // check the function parameters
  // reuse comp_bytes_until_mismatch()
  umax int_pos = comp_bytes_until_mismatch(arr1, arr2, size);
  if (int_pos == size)
    return true;
  return false;
}

// ignore_bytes() function
// function to skip bytes found from a position in an array.
// The function will return the position of the first byte found
// on data that isn't present on the skip string.
byte * ignore_bytes(byte * src, umax src_size, byte * skip, umax skip_size)
{
  // check the function parameters
  if (check_byte_array(src, src_size) == false || check_byte_array(skip, skip_size) == false)
    goto err;
  
  // variable to exit the for loop
  bool ch_skip_read = false;
  for (umax i = 0; i < src_size; i++) {
    // read skip
    for (umax j = 0; j < skip_size; j++)
      ch_skip_read = ch_skip_read || (src[i] == skip[j]);
    // none of the characters in skip was matched
    if (ch_skip_read == false)
      return src + i;
  }
  
  err: // nothing different than skip data was found in src
  return NULL;  
}

// search_byte_pattern() function
// function to search for a given byte sequence in a byte array.
// It will return the first occurrence of the pattern
byte * search_byte_pattern(byte * src, umax ssize, byte * pattern, umax psize)
{
  // check function parameters
  if (check_byte_array(src, ssize) == false || check_byte_array(pattern, psize) == false)
    goto err;
  
  // search in array for said pattern
  for (umax i = 0; i + psize <= ssize; i++)   
    if (comp_bytes(pattern, src + i, psize))
      return src + i;
  
  // failure
  err:
  return NULL; 
}

// byte_shift() function
// shift bytes on memory to the right or the left 1 position
// it will behave as the bitshift operations but for byte arrays
bool byte_shift(byte * arr, umax size, byteshift_type shift)
{
  // check function parameters
  if (check_byte_array(arr, size) == false || check_byteshift_type(shift) == false)
    return false;
  
  // set the variables for the next for loop
  // arr = arr;
  char shift_inc = 1;  
  if (shift == BYTESHIFT_LEFT) {
    arr = arr + size - 1;
    shift_inc = -1;
  }
  
  // do the byteshift
  byte new_byte = 0, old_byte = 0;
  for (umax i = 0; i < size; i++) {
    new_byte = *(arr);
    *(arr) = old_byte;
    old_byte = new_byte;
    arr += shift_inc;
  }
  // byte shift done
  return true;
}

// byte_n_shift() function
// reuses byte_shift "n" times
bool byte_n_shift(byte * arr, umax size, byteshift_type shift, umax n)
{
  // check n
  if (n == 0)
    return false;
  
  // do n single byte shifts
  while (n-- > 0)
    // if a call of byte_shift cannot be done then end the function
    if (byte_shift(arr, size, shift) == false)
      return false;
  
  // byte shift done
  return true;
}

// byte_circ_shift() function
// shift the bytes of a byte array keeping the bytes that are going to
// get discarded. These bytes are put (depending on the shift direction) on
// the end or at the start of the byte array being byte shifted
bool byte_circ_shift(byte * arr, umax size, byteshift_type shift)
{
  // check the function parameters
  if (check_byte_array(arr, size) == false || check_byteshift_type(shift) == false)
    return false;
  
  // save the byte that will get lost on the byteshift
  byte princess_byte = (shift == BYTESHIFT_RIGHT ? arr[size - 1] : arr[0]);
  byte_shift(arr, size, shift);
  
  // add the princess byte
  if (shift == BYTESHIFT_LEFT)
    arr[size - 1] = princess_byte;
  else /* BYTESHIFT_RIGHT */
    arr[0] = princess_byte;
  
  // circular byte shift done
  return true;
}

// byte_n_circ_shift() function
// same as byte_circ_shift() but n times
bool byte_n_circ_shift(byte * arr, umax size, byteshift_type shift, umax n)
{
  // check n
  if (n == 0)
    return false;
  
  // do n single circular byte shifts
  while (n-- > 0)
    // if a call of byte_shift cannot be done then end the function
    if (byte_circ_shift(arr, size, shift) == false)
      return false;
  
  // circular byte shift done
  return true;
}

// create_bytarr()
// function to create a bytarr structure
// the arr pointer may be NULL and the function
// will do the same as create_mem_space()
bytarr * create_bytarr(byte * arr, umax size)
{
  // check params
  bytarr * rtn = create_mem_space(size);
  if (rtn == NULL)
    return NULL;
  
  // copy the elements from arr into the structure
  cp_mem_bytes(arr, size, rtn -> ptr);
  return rtn;
}

// check_bytarr()
// function to check if the members of a bytarr structure are valid
bool check_bytarr(bytarr * arr)
{
  return (arr != NULL && arr -> ptr != NULL && arr -> size != 0);
}

// free_bytarr()
// function to free the memory used by a bytarr structure
umax free_bytarr(bytarr * arr)
{
  return free_mem_space((mem_space *) arr);
}

// print_bytarr()
// function to print the information on a bytarr array
umax print_bytarr(byte * src, umax size)
{
  // check params
  if (src == NULL || size < BYTARR_SIZE)
    return 0;  
  // print the structure
  bytarr * arr = (bytarr *) src;
  printf("BYTARR structure info:\n");
  printf("Pointer: %p\n", arr -> ptr);
  printf("Size (bytes): %llu\n", arr -> size);  
  printf("Contents: ");
  print_byte_array(arr -> ptr, arr -> size);
  putchar('\n');
  return BYTARR_SIZE;
}

// cp_bytarr()
// function to copy the contents of a byte array into another byte array
// the copy will be successful only if dest -> size is greater or equal to src -> size
// also, the function will return a "temporal" bytarr with the last write position on dest
bytarr cp_bytarr(bytarr * src, bytarr * dest)
{
  // check params
  if (check_bytarr(src) == false
      || check_bytarr(dest) == false
      || src -> size > dest -> size)
    return (bytarr) {NULL, 0};
  
  // do the copy and return the temporal structure
  return (bytarr) {cp_mem_bytes(src -> ptr, src -> size, dest -> ptr), dest -> size - src -> size};
}

// comp_bytarrs_until_mismatch()
// the function will return an integer indicating the first non-equal byte in the comparison
// the comparison will only be done with the lowest array size
umax comp_bytarrs_until_mismatch(bytarr * arr1, bytarr * arr2)
{
  // check params
  if (check_bytarr(arr1) == false || check_bytarr(arr2) == false)
    return 0;
  
  // reuse comp_bytes_until_mismatch()
  return comp_bytes_until_mismatch(arr1 -> ptr,
                                   arr2 -> ptr,
                                   arr1 -> size < arr2 -> size ? arr1 -> size : arr2 -> size);
}

// comp_bytarrs()
// function to check if 2 bytarrs are equivalent (contents and size)
bool comp_bytarrs(bytarr * arr1, bytarr * arr2)
{
  // check params
  if (check_bytarr(arr1) == false || check_bytarr(arr2) == false || arr1 -> size != arr2 -> size)
    return false;
  
  // reuse comp_bytes()
  return comp_bytes(arr1 -> ptr, arr2 -> ptr, arr1 -> size);
}

// ignore_bytarr_bytes()
// ignore the bytes from a bytarr structure
// the function will return a bytarr to the part skipped from arr
bytarr ignore_bytarr_bytes(bytarr * arr, bytarr * skip)
{
  // check params
  if (check_bytarr(arr) == false || check_bytarr(skip) == false)
    return (bytarr) {NULL, 0};
    
  // reuse ignore_bytes()
  byte * ptr = ignore_bytes(arr -> ptr, arr -> size, skip -> ptr, skip -> size);
  return (ptr == NULL ?
          (bytarr) {NULL, 0} :
          (bytarr) {ptr, arr -> size - (ptr - arr -> ptr)});
}

// search_bytarr()
// functiont to search for a byte array inside another one
bytarr search_bytarr(bytarr * src, bytarr * search)
{
  // check params
  if (check_bytarr(src) == false || check_bytarr(search) == false)
    return (bytarr) {NULL, 0};
  
  // reuse search_byte_pattern()
  byte * ptr = search_byte_pattern(src -> ptr, src -> size, search -> ptr, search -> size);
  return (bytarr) {ptr, search -> size};
}

// bytarr_shift()
bool bytarr_shift(bytarr * arr, byteshift_type shift)
{
  // check params
  if (check_bytarr(arr) == false || check_byteshift_type(shift) == false)
    return false;
    
  // reuse byte_shift()
  return byte_shift(arr -> ptr, arr -> size, shift);
}

// bytarr_n_shift()
bool bytarr_n_shift(bytarr * arr, byteshift_type shift, umax n)
{
  // check params
  if (check_bytarr(arr) == false || check_byteshift_type(shift) == false || n == 0)
    return false;
    
  // reuse byte_shift()
  return byte_n_shift(arr -> ptr, arr -> size, shift, n);
}

// bytarr_circ_shift()
bool bytarr_circ_shift(bytarr * arr, byteshift_type shift)
{
  // check params
  if (check_bytarr(arr) == false || check_byteshift_type(shift) == false)
    return false;
    
  // reuse byte_shift()
  return byte_circ_shift(arr -> ptr, arr -> size, shift);
}

// bytarr_n_circ_shift()
bool bytarr_n_circ_shift(bytarr * arr, byteshift_type shift, umax n)
{
  // check params
  if (check_bytarr(arr) == false || check_byteshift_type(shift) == false || n == 0)
    return false;
    
  // reuse byte_shift()
  return byte_n_circ_shift(arr -> ptr, arr -> size, shift, n);
}

// ins_bytarr() function
// function to insert bytes in a bytarr at some position
bytarr * ins_bytarr(bytarr * arr, umax arr_ins_pos, bytarr * bytes)
{
  // check params
  bytarr * new = NULL;
  if (check_bytarr(arr) == false || check_bytarr(bytes) == false || arr_ins_pos > arr -> size)
      goto err;
  
  // allocate space for the new bytarr structure
  new = create_bytarr(NULL, arr -> size + bytes -> size);
  if (check_bytarr(new) == false)
    goto err;
  
  // copy arr into dest up to arr_ins_pos, consider case arr_ins_pos == 0
  bytarr tmp = {arr -> ptr, arr_ins_pos};
  if (arr_ins_pos != 0) {
    tmp = cp_bytarr(&tmp, new);
  } else
    tmp = *new;
  // copy bytes into tmp
  tmp = cp_bytarr(bytes, &tmp);
  // copy the rest of data from arr into dest
  cp_bytarr(&((bytarr) {arr -> ptr + arr_ins_pos, arr -> size - arr_ins_pos}), &tmp);
  
  // free the memory occupied by arr -> ptr (if it was manually allocated)
  free_bytarr(arr);
  return new;
  err: // error
  free_bytarr(new);
  return NULL;
}

// rm_bytarr()
// function to remove a bytarr portion of a bytarr structure
umax rm_bytarr(bytarr * arr, umax rm_pos, umax rm_size)
{
  // check params
  if (check_bytarr(arr) == false || (rm_pos + rm_size) > (arr -> size) || rm_size == 0)
    return 0;
  
  // otherwise remove the bytes
  bytarr_n_shift(&((bytarr) {arr -> ptr + rm_pos, arr -> size - rm_pos}), BYTESHIFT_LEFT, rm_size);
  return arr -> size - rm_size;
}

//~ // function to insert bytes (from src) in an array (target at insert_pos) loaded in memory
//~ // the function will return the pointer to the new array allocated
//~ byte * insert_bytes(byte * target, umax target_size, byte * insert_pos, byte * src, umax src_size)
//~ {
  //~ // check params
  //~ byte * new = NULL;
  //~ if (target == NULL || target_size == 0 || insert_pos == NULL || src == NULL || src_size == 0)
    //~ goto err;
  //~ // check if insert_pos is actually contained within [target, target + target_size - 1]
  //~ smax tmp = (byte *) insert_pos - (byte *) target;
  //~ if (tmp < 0 || tmp > target_size)
    //~ goto err;
  //~ // get a new container for the new data
  //~ new = allocate_memory(1, target_size + src_size);
  //~ if (new == NULL)
    //~ goto err;
  
  //~ // copy the bytes from target from target to insert_pos
  //~ cp_mem_bytes(target, tmp, new);
  //~ // copy the bytes from src
  //~ cp_mem_bytes(src, src_size, new + tmp);
  //~ // copy the rest of the bytes from insert_pos to target + target_size - 1
  //~ cp_mem_bytes(target + tmp, target_size - tmp, new + tmp + src_size);
  
  //~ // free the memory occupied by target (if it was manually allocated)
  //~ free_memory(target);
  //~ return new;
  //~ err: // error
  //~ free_memory(new);
  //~ return NULL;
//~ }

//~ // function to remove bytes (from remove_pos) in an array (data at rm) loaded in memory
//~ // the function will return the data array changed size
//~ umax remove_bytes(byte * target, umax target_size, byte * remove_pos, umax remove_size)
//~ {
  //~ // check params
  //~ if (target == NULL || target_size == 0 || remove_pos == NULL || remove_size == 0)
    //~ goto err;
  //~ // check if remove_pos is actually contained within [target, target + target_size - 1]
  //~ smax tmp = (byte *) remove_pos - (byte *) target;
  //~ if (tmp < 0 || tmp > target_size)
    //~ goto err;   
  
  //~ // remove the bytes selected by using the byteshift function
  //~ byte_n_shift(remove_pos, target_size - tmp, BYTESHIFT_LEFT, remove_size);
  //~ // return the "new" size of the array
  //~ return target_size - remove_size;
  //~ err: // error
  //~ return 0;
//~ }
